﻿module internal HyperF.IpHlpApi

open System
open System.Runtime.InteropServices
open System.ComponentModel
open System.Net



// Declare the GetIpNetTable function.
[<DllImport("IpHlpApi.dll")>]
let private GetIpNetTable(pIpNetTable:IntPtr, pdwSize:byref<int>, bOrder:bool) : int = failwith "extern"

// The insufficient buffer error.
let private ERROR_INSUFFICIENT_BUFFER = 122


type AddressEntry (ip:IPAddress, mac:string) =
  member x.IpAddress = ip
  member x.MacAddress = mac


let getIpNetTable () =
    // The number of bytes needed.
    let mutable bytesNeeded = 0

    // Call the function, expecting an insufficient buffer.
    // Useful to retrieve the number of bytes needed
    let result = GetIpNetTable(IntPtr.Zero, &bytesNeeded, false)
    if result <> ERROR_INSUFFICIENT_BUFFER then
      raise (new Win32Exception(result))

    // Allocate the memory.
    let buffer = Marshal.AllocCoTaskMem(bytesNeeded)
    // Make the call again. If it did not succeed, then raise an error.
    let result = GetIpNetTable(buffer, &bytesNeeded, false)
    if result <> 0 then
      raise (new Win32Exception(result))

    // the first byte is the number of entries
    let numEntries = Marshal.ReadInt32(buffer)
    let buffer = buffer + 4n

    // copy the buffer from IntPtr to byte array
    let bufferByte = Array.zeroCreate<byte> (numEntries*24)
    Marshal.Copy(buffer, bufferByte, 0, numEntries*24) |> ignore

    // parse the buffer. Each entry is 24 bytes:
    // - 4: entry index
    // - 4: 
    // - 6: mac address
    // - 2: (padding?)
    // - 4: ip address
    // - 4
    [| for b = 0 to numEntries-1 do
         let baseOff = b*24
         let mac = [| for i = 0 to 5 do yield (bufferByte.[baseOff+8+i].ToString("X2")) |] |> String.Concat
         let ip = [| for i in [11..-1..8] do yield (bufferByte.[baseOff+8+i].ToString("X2")) |] |> String.Concat
         let ipAddr = System.Net.IPAddress( System.Int64.Parse(ip, System.Globalization.NumberStyles.HexNumber) )
         yield new AddressEntry(ipAddr, mac) |]